<?php
/**
 * The MIT License
 *
 * Copyright (c) <year> <copyright holders>
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 *
 * Classe utilizzata dal metodo ->prepare($sql) dell'istanza BXDDb() per eseguire il bind
 * dei parametri presenti nella query. Il metodo finale execute() restituisce un'istanza
 * della classe BXDDbResultQuery() dove è possibile eseguire il fetch dei risultati
 *
 * @author Damiano Ciarla
 * @package lib
 * @subpackage db/prepare
 */
class BXDDbPrepare
{
	/**
	 * Prepare della query proveniente dalla classe BXDDb
	 *
	 * @var PDOStatement
	 */
	private $sth;

	/**
	 * Lista di tutti i parametri presenti nella query (?|:nome_parametro)
	 *
	 * @var array
	 */
	private $params = array();
	
	/**
	 * Numero dei parametri con il dollaro presenti nella query
	 *
	 * @var int
	 */
	private $countParamsNameless = 0;

	/**
	 * Numero dei parametri con il dollaro presenti nella query e che non sono ancora stati bindati
	 *
	 * @var int
	 */
	private $countParamsNamelessRemaining = 0;

	/**
	 * Numero dei parametri presenti nella query che hanno un nome
	 *
	 * @var int
	 */
	private $countParamsAsName = 0;

	/**
	 * Numero dei parametri presenti nella query che hanno un nome e che non sono ancora stati bindati
	 *
	 * @var int
	 */
	private $countParamsAsNameRemaining = 0;

	private $countParamsBinding = 0;

	public function __construct(PDOStatement $sth)
	{
		$this->sth = $sth;

		$this->_parseParams();
	}

	/**
	 * Verifica dei parametri contenuti nella query
	 */
	private function _parseParams()
	{
		preg_match_all('/\?|:[a-zA-Z0-9_]+/', $this->sth->queryString, $params);
		
		if(count($params[0]) > 0)
		{
			foreach($params[0] as $value)
			{
				if($value == "?")
				{
					$this->params[$this->countParamsNameless] = array('value' => null, 'type' => null);
					$this->countParamsNameless++;
					$this->countParamsNamelessRemaining++;
				}
				elseif($value[0] == ":")
				{
					$this->params[substr($value, 1)] = array('value' => null, 'type' => null);
					$this->countParamsAsName++;
					$this->countParamsAsNameRemaining++;
				}
			}
		}
	}

	/**
	 * Metodo utilizzato per effetture il bind dei parametri presenti nella query
	 *
	 * @param string $parameter Nome del parametro da bindare (?|:nome_parametro)
	 * @param mixed $value Valore da assegnare al parametro
	 * @param int $dataType [opzionale] Tipologia del parametro. Fare riferimento ai valori PDO::PARAM_*
	 *
	 * @return BXDDbPrepare
	 */
	public function bindParam($parameter, $value, $dataType = null)
	{
		if($parameter == "?")
		{
			if($this->countParamsNamelessRemaining > 0)
			{
				$queryParam = $this->countParamsBinding;
				$this->countParamsBinding++;
				$this->countParamsNamelessRemaining--;
			}
			else throw new BXDDbException($this->sth->queryString, BXDDbException::TOO_MANY_PARAMS);
		}
		else
		{
			$queryParam = (($parameter[0] == ":") ? substr($parameter, 1) : $parameter);

			if(key_exists($queryParam, $this->params))
			{
				$this->countParamsAsNameRemaining--;
			}
			else throw new BXDDbException(array($queryParam, $this->sth->queryString), BXDDbException::PARAM_NOT_FOUND_INTO_QUERY);
		}

		$this->params[$queryParam]['value'] = $value;
		$this->params[$queryParam]['type'] = (($dataType === null) ? $this->_checkDataType($value) : $dataType);

		return $this;
	}

	/**
	 * Esecuzione della query
	 *
	 * @return BXDDbResult
	 */
	public function execute()
	{
		if($this->countParamsNameless > 0 && $this->countParamsAsName > 0)
		{
			throw new BXDDbException($this->sth->queryString, BXDDbException::MIXED_TYPE_BIND);
		}
		elseif($this->countParamsNamelessRemaining == 0 && $this->countParamsAsNameRemaining == 0)
		{
			foreach($this->params as $parameter => $data)
			{
				if(is_numeric($parameter))
					$parameter = $parameter + 1;
				else
					$parameter = ":" . $parameter;

				$this->sth->bindParam($parameter, $data['value'], $data['type']);
			}

			try
			{
				$this->sth->execute();

				Bxd::getInstance()->lib('db')->loadClass("DbResult");
				
				return new BXDDbResult($this->sth);
			}
			catch(PDOException $e)
			{
				throw new BXDDbException($e->getMessage(), BXDDbException::INVALID_SQL_QUERY);
			}
		}
		elseif($this->countParamsNamelessRemaining > 0 || $this->countParamsAsNameRemaining > 0)
		{
			throw new BXDDbException($this->sth->queryString, BXDDbException::INSUFFICENT_BIND_PARAMS);
		}
	}

	/**
	 * Metodo che esegue il check automatico del tipo di dato da bindare
	 *
	 * @param mixed $value Valore da controllare
	 *
	 * @return int|boolean Risultato del controllo
	 */
	private function _checkDataType($value)
	{
		if(is_int($value))
		{
			$param = PDO::PARAM_INT;
		}
		elseif(is_bool($value))
		{
			$param = PDO::PARAM_BOOL;
		}
		elseif(is_null($value))
		{
			$param = PDO::PARAM_NULL;
		}
		elseif(is_string($value))
		{
			$param = PDO::PARAM_STR;
		}
		else
		{
			$param = false;
		}

		return $param;
	}
}
?>
